package org.jeecg.common.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.LongAdder;

public class ThreadPoolUtil {
    private static final Logger LG = LoggerFactory.getLogger("ThreadPoolTask");
    private static ThreadPoolExecutor executor = new ThreadPoolExecutor(500, 1000, 10 * 60,
            TimeUnit.SECONDS, new LinkedBlockingDeque<>(), new ThreadPoolExecutor.AbortPolicy());

    /**
     * 添加同步线程
     * @param title
     * @param batchTasks
     */
    public static void execSyncTasks(String title, List<Runnable> batchTasks){
        execSyncTasks(title, batchTasks.size(), batchTasks);
    }

    /**
     * 添加异步线程任务，并锁定并发数量
     * @param title         执行主题
     * @param max           最大并发数量
     * @param batchTasks    批量任务类型
     */
    public static void execSyncTasks(String title, int max, List<Runnable> batchTasks){
        try{
            // 并发控制器
            final Semaphore semaphore = new Semaphore(max);
            // 并发任务计数器
            final LongAdder longAdder = new LongAdder();
            // 所有任务完成计数器
            final CountDownLatch downLatch = new CountDownLatch(batchTasks.size());
            long btime = System.currentTimeMillis();
            LG.info("【{}】准备将任务添加到线程池中。", title);
            execTask(() -> {
                for(int i=0; i<batchTasks.size(); i++){
                    Runnable runnable = batchTasks.get(i);
                    try {
                        semaphore.acquire();
                        executor.execute(() -> {
                            try{
                                longAdder.increment();
                                runnable.run();
                            } catch (Exception e) {
                                LG.error("【" + title + "】执行线程失败。", e);
                            } finally {
                                downLatch.countDown();
                                longAdder.decrement();
                                semaphore.release();
                                LG.info("【" + title + "】任务总数共{}个，正在执行的{}个，剩余任务{}个。", batchTasks.size(), longAdder.sum(), downLatch.getCount());
                            }
                        });
                    } catch (InterruptedException e) {
                        LG.error("【" + title + "】执行线程失败。", e);
                    }
                }
            });
            LG.info("【{}】任务添加成功。准备等待执行。", title);
            downLatch.await();
            long etime = System.currentTimeMillis();
            long time = (etime - btime)/1000;
            LG.info("【{}】任务执行耗时{}秒。", title, time);
        } catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    /**
     * 异步执行单个任务
     * @param task
     */
    public static void execTask(Runnable task){
        executor.execute(()->{
            task.run();
        });
    }

    /**
     * 异步执行单个任务, 本次事务提交成功后，执行任务，否则抛弃
     * @param task
     */
    public static void execTxTask(Runnable task){
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCompletion(int status) {
            if (status == TransactionSynchronization.STATUS_COMMITTED) {
                // 事务已回滚，执行需要的操作
                executor.execute(()->{
                    task.run();
                });
            } else {
                LG.warn("事务提交失败，忽略此次任务");
            }
            }
        });
    }

    /**
     * 添加异步线程任务，并锁定并发数量
     * @param title         执行主题
     * @param max           最大并发数量
     * @param batchTasks    批量任务类型
     */
    public static void execTasks(String title, int max, List<Runnable> batchTasks){
        executor.execute(() -> {
            execSyncTasks(title, max, batchTasks);
        });
    }

    /**
     * 添加同步等待线程任务，并锁定并发数量
     * @param title         执行主题
     * @param max           最大并发数量
     * @param partSize      批量任务类型
     * @param list          数据集合
     * @param subTask       任务执行任务
     * @param <T>
     */
    public static <T> void execTasks(String title, int max, int partSize, List<T> list, SubTask<T> subTask){
        List<Runnable> batchTasks = splitTasks(title, max, partSize, list, subTask);
        execTasks(title, max, batchTasks);
    }

    /**
     * 添加同步等待线程任务，并锁定并发数量, 本次事务提交成功后，执行任务，否则抛弃
     * @param title         执行主题
     * @param max           最大并发数量
     * @param partSize      批量任务类型
     * @param list          数据集合
     * @param subTask       任务执行任务
     * @param <T>
     */
    public static <T> void execTxTasks(String title, int max, int partSize, List<T> list, SubTask<T> subTask){
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCompletion(int status) {
            if (status == TransactionSynchronization.STATUS_COMMITTED) {
                // 事务已回滚，执行需要的操作
                List<Runnable> batchTasks = splitTasks(title, max, partSize, list, subTask);
                execTasks(title, max, batchTasks);
            } else {
                LG.warn("事务提交失败，忽略此次任务");
            }
            }
        });
    }

    /**
     * 添加同步等待线程任务，并锁定并发数量
     * @param title         执行主题
     * @param max           最大并发数量
     * @param partSize      批量任务类型
     * @param list          数据集合
     * @param subTask       任务执行任务
     * @param <T>
     */
    public static <T> void execSyncTasks(String title, int max, int partSize, List<T> list, SubTask<T> subTask){
        List<Runnable> batchTasks = splitTasks(title, max, partSize, list, subTask);
        execSyncTasks(title, max, batchTasks);
    }

    private static <T> List<Runnable> splitTasks(String title, int max, int partSize, List<T> list, SubTask<T> subTask){
        int page = list.size() / partSize;
        page = (list.size() % partSize) == 0? page: (page + 1);
        List<Runnable> batchTasks = new ArrayList<>();
        LG.info("【{}】准备分隔集合数据，总共:{}, 页大小:{}, 最终页数:{}。", title, list.size(), partSize, page);
        for(int i=0; i<page; i++){
            int formIndex = i * partSize;
            int toIndex = (i + 1) * partSize;
            toIndex = toIndex > list.size()? list.size(): toIndex;
            List<T> subList = list.subList(formIndex, toIndex);
            batchTasks.add(()->{
                subTask.run(subList);
            });
        }
        return batchTasks;
    }

    public interface SubTask<T>{
        public void run(List<T> list);
    }
}
